package gui.typeEditors;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public abstract class TypeEditor<T> extends JPanel
	{
	private static final long serialVersionUID = -8989708150059028721L;
	
	ArrayList<ActionListener> actionListeners = new ArrayList<ActionListener>();
	protected boolean doNotFireEvents = false;
	protected boolean editable = true;
	/**Get the value. For primitive types, the proper encapsulation is returned.*/
	public abstract T getValue();

	/**The value from this method is used for writing the value to a file.
	 * As such, it should be compatible with the methods defined in TypeParser.*/
	public String getValueString()
		{
		T value = getValue();
		if(value == null)
			return "null";
		else
			return value.toString();
		}
	/**Calling this method never fires an action event. 
	 * @throws ClassCastException if the value does not have the correct data type.*/
	public abstract void setValue(T val);
	/**Thread handling flashing light when changing the value.*/
	private FlashThread flashThread;
	/**Add action listener to be notified when the value is changed from the component. 
	 * The listener will never be notified when the value is set programmatically.*/
	
	protected TypeEditor()
		{
		super();

		this.setLayout(new BorderLayout());
		}
	
	public void addActionListener(ActionListener e)
		{
		actionListeners.add(e);
		}
	/**Remove the instance from the list of action listeners.*/
	public void removeActionListener(ActionListener e)
		{
		actionListeners.remove(e);
		}
	/**Fire action events, but does nothing if doNotFireEvents*/
	protected void fireActionEvents()
		{
		if(doNotFireEvents)
			return;
		ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "Value Changed");
		for(ActionListener a : actionListeners)
			a.actionPerformed(e);
		}
	
	/**
	 * Runs a task in the given time in milliseconds. The task will be postponed if this is called in rapid succession. 
	 * The original task will then be performed, not the ones specified in the successive calls.
	 * @param task The task to execute.
	 * @param ms if 0, invoke the runnable on the swing thread. Otherwise, invoke in this amount of milliseconds from now.
	 * @return null if the task is complete, the currently waiting thread otherwise.
	 */
	protected synchronized FlashThread flashThread(Runnable task, int ms)
		{
		if(ms == 0)
			{
			if(System.currentTimeMillis() > flashThread.ms)
				{
				SwingUtilities.invokeLater(task);
				flashThread = null;
				}
			}
		else if(flashThread == null)
			flashThread = new FlashThread(task, ms);
		else
			flashThread.ms(ms);
		return flashThread;
		}
	
	private class FlashThread extends Thread
		{
		long ms;
		Runnable task;
		FlashThread(Runnable task, int ms)
			{
			this.task = task;
			this.ms = System.currentTimeMillis()+ms;
			start();
			}
		
		@Override
		public void run()
			{
			boolean run = true;
			
			while(run)
				{
				long time = System.currentTimeMillis();
				if(time > ms(0))
					{
					if(flashThread(task, 0) == null)
						run = false;
					}
				}
			}
		
		synchronized int ms(int ms)
			{
			if(ms > 0)
				this.ms = System.currentTimeMillis()+ms;
			return ms;
			}
		}
	
	@Override
	public void setToolTipText(String text)
		{
		super.setToolTipText(text);
		setToolTipTextToSubComponents(text);
		}
	public abstract void setToolTipTextToSubComponents(String text);
	
	@Override
	public final void setEnabled(boolean en)
		{
		if(editable)
			{
			super.setEnabled(en);
			setSubComponentsEnabled(en);
			}
		}
	public abstract void setSubComponentsEnabled(boolean en);

	/**
	 * Use only when it is known that the object is of the correct value.
	 * @param value the new Value
	 * @see TypeEditor#setValue(Object)
	 */
	@SuppressWarnings("unchecked")
	public void setObjectValue(Object value)
		{
		setValue((T) value);
		}
	/**Can be used to permanently disable the editor.*/
	public void disableEditability()
		{
		//Note: do not reverse the following order.
		setEnabled(false);
		editable = false;
		}

	public boolean shouldGrowHeight()
		{
		return false;
		}
	}
